﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CrusadeGmTool.ComponentModel;
using CrusadeGmTool.Diagnostics;
using CrusadeGmTool.Net.IO;
using System.Net.Sockets;
using System.Net;

namespace CrusadeGmTool.Net
{
   public sealed class NetworkContext:NotifierBase, INetworkContext
    {
       private Logger _log;
       
       private Socket _serverSocket;
       private IPAddress _serverAddress;
       private IPEndPoint _serverEndPoint;

       private PacketHandler[] _handlers;
       private PacketHandler[][] _extendedHandlers;

       private bool _decompressionEnabled;
       private bool _connected;
       private bool _connecting;

       private byte[] _receiveBuffer;
       private int _receiveBufferPosition;

       private long _bytesReceived;
       private long _bytesSent;
       private bool _logPackets;


       #region Properties
       public bool LogPackets
       {
           get { return _logPackets; }
           set
           {
               if (_logPackets != value)
               {
                   _logPackets = value;
                   RaisePropertyChanged("LogPackets");
               }
           }
       }

       public long BytesReceived
       {
           get { return _bytesReceived; }
           private set
           {
               if (_bytesReceived != value)
               {
                   _bytesReceived = value;
                   RaisePropertyChanged("BytesReceived");
               }
           }
       }

       public long BytesSent
       {
           get { return _bytesSent; }
           private set
           {
               if (_bytesSent != value)
               {
                   _bytesSent = value;
                   RaisePropertyChanged("BytesSent");
               }
           }
       }

       public IPAddress ServerAddress
       {
           get { return _serverAddress; }
           private set
           {
               if (_serverAddress != value)
               {
                   _serverAddress = value;
                   RaisePropertyChanged("ServerAddress");
               }
           }
       }

       public bool DecompressionEnabled
       {
           get { return _decompressionEnabled; }
           set
           {
               if (_decompressionEnabled != value)
               {
                   _decompressionEnabled = value;
                   RaisePropertyChanged("DecompressionEnabled");
               }
           }
       }

       public bool Connected
       {
           get { return _connected; }
           private set
           {
               if (_connected != value)
               {
                   _connected = value;
                   OnConnectedChanged(this, EventArgs.Empty);
                   RaisePropertyChanged("Connected");
               }
           }
       }

       public bool Connecting
       {
           get { return _connecting; }
           set
           {
               if (_connecting != value)
               {
                   _connecting = value;
                   RaisePropertyChanged("Connecting");
               }
           }
       }

       
       #endregion

       public event EventHandler ConnectedChanged;

       public NetworkContext()
       {
           _log = new Logger(this.GetType());
           _receiveBuffer = new byte[0x10000];

           _handlers = new PacketHandler[0x100];
           _extendedHandlers = new PacketHandler[0x100][];
       }

       private void OnConnectedChanged(object sender, EventArgs e)
       {
           if (ConnectedChanged != null)
           {
               ConnectedChanged(sender, e);
           }
       }

       public void Register(int id, string name, int? length, Action<PacketReader> receiveAction)
       {
           Register(id, name, length, false, receiveAction);
       }

       public void Register(int id, string name, int? length, bool compressed, Action<PacketReader> receiveAction)
       {
           if (id > Byte.MaxValue)
           {
               throw new NetworkException(String.Format("Unable to register Packet {0:X2} Because his it is bigger than a byte", id));
           }
           _handlers[id] = new PacketHandler(id, name, length, compressed, receiveAction);
       }

       public void Unregister(int id)
       {
           _handlers[id] = null;
       }

       public void RegisterExtended(int extendedId, int subId, string name, int? length, Action<PacketReader> receiveAction)
       {
           if (extendedId > byte.MaxValue)
           {
               throw new NetworkException(string.Format("Unable to register extended packet id {0:X2} because it is greater than byte.MaxValue", extendedId));
           }

           if (subId > ushort.MaxValue)
           {
               throw new NetworkException(string.Format("Unable to register sub packet id {0:X2} because it is greater than ushort.MaxValue", subId));
           }

           if (_extendedHandlers[extendedId] == null)
           {
               _extendedHandlers[extendedId] = new PacketHandler[0x100];
           }

           _extendedHandlers[extendedId][subId] = new PacketHandler(subId, name, length, false, receiveAction);
       }

       public bool Connect(string ipAdressOrHostName, int port)
       {
           if (Connected)
           {
               Disconnect();
           }

           Connecting = true;
           BytesReceived = 0;
           BytesSent = 0;

           bool success = true;
           try
           {
               if (!IPAddress.TryParse(ipAdressOrHostName, out _serverAddress))
               {
                  IPAddress[] ipAdresses = Dns.GetHostAddresses(ipAdressOrHostName);
                   if (ipAdresses.Length == 0)
                   {
                       throw new NetworkException(string.Format("Unable to the the IP Adress for {0}", ipAdressOrHostName));
                   }
                   else
                   {
                       ServerAddress = ipAdresses[0];
                   }
               }
               _log.Debug(String.Format("Connecting to {0} ({1}) ...", ipAdressOrHostName, ServerAddress));
               
               _serverEndPoint = new IPEndPoint(_serverAddress, port);
               _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
               _serverSocket.Connect(_serverEndPoint);

               if (_serverSocket.Connected)
               {
                   SocketState state = new SocketState(_serverSocket, ushort.MaxValue);
                   this.SendSeedPacket();
                   _serverSocket.BeginReceive(state.Buffer,0, state.Buffer.Length, SocketFlags.None, OnReceive, state); 
               }
           }
           catch (Exception e)
           {
               success = false;
               _log.Fatal(e);
           }

           Connecting = false;
           Connected = success;

           return success;
       }

       private void SendSeedPacket()
       {
           PacketWriter writer = new PacketWriter();
           byte[] seed = new byte[4] { 1, 2, 3, 4 };
           this.Send(seed, 0, 4);
       }

       public void Disconnect()
       {
           if (_serverSocket != null)
           {
               ServerAddress = null;
               _serverSocket.Shutdown(SocketShutdown.Both);
               _serverSocket.Close();
               _serverSocket = null;
               _serverEndPoint = null;
               _decompressionEnabled = false;
               _log.Debug("Disconnected");
               Connected = false;
           }
       }

       public bool Send(Packet packet)
       {
           byte[] buffer = packet.Compile();

           if (_logPackets)
           {
               _log.Debug("Client -> Server");
               _log.Debug(packet.ToString());
           }
           return Send(buffer, 0, packet.Length ?? buffer.Length);
       }

       public bool Send(byte[] buffer, int offset, int length)
       {
           bool success = true;

           if (buffer == null || buffer.Length == 0)
           {
               throw new NetworkException("Unable to send : buffer was null or empty");
           }

           if (_logPackets)
           {
               _log.Debug("\n{0}", Utility.FormatBuffer(buffer, length));
           }

           try
           {
               BytesSent += length;
               _serverSocket.Send(buffer, offset, length, SocketFlags.None);
           }
           catch (Exception e)
           {
               _log.Fatal(e);
               success = false;
           }
           return success;
       }

       private void OnReceive(IAsyncResult result)
       {
           if (!_connected)
           {
               return;
           }

           SocketState state = result.AsyncState as SocketState;

           if (state == null)
           {
               _log.Warn("Socket State was null");
               return;
           }

           try
           {
               Socket socket = state.Socket;
               if (socket.Connected)
               {
                   int length = socket.EndReceive(result);
                   if (length > 0)
                   {
                       byte[] buffer = state.Buffer;
                       Buffer.BlockCopy(buffer, 0, _receiveBuffer, _receiveBufferPosition, length);
                       BytesReceived += length;
                       _receiveBufferPosition += length;
                       int index = 0;

                       while (index < _receiveBufferPosition)
                       {
                           int packetLength = length;
                           bool extended = false;
                           PacketHandler handler = GetHandler(_receiveBuffer, index, ref packetLength, ref extended);

                           if (_receiveBufferPosition - index >= packetLength)
                           {
                               byte[] packetBuffer = new byte[packetLength];
                               Buffer.BlockCopy(_receiveBuffer, index, packetBuffer, 0, packetLength);

                               if (_logPackets)
                               {
                                   _log.Debug("Server - > Client");
                                   _log.Debug("Id: 0x{0:X2} Name: {1} Length: {2}", _receiveBuffer[index], handler == null ? "Unknown" : handler.Name, packetLength);
                                   _log.Debug("\n{0}", Utility.FormatBuffer(packetBuffer, packetLength));
                               }

                               if (handler != null)
                               {
                                   PacketReader reader = PacketReader.CreateInstance(packetBuffer, packetLength, handler.Length.HasValue, extended);
                                   if (handler.Compressed)
                                   {
                                       handleCompressedPacket(buffer, ref extended, ref handler, ref reader);
                                   }
                                   else
                                   {
                                       try
                                       {
                                           handler.ReceiveAction.Invoke(reader);
                                       }
                                       catch (Exception e)
                                       {
                                           _log.Error(e);
                                           throw;
                                       }
                                   }
                               }
                               index += packetLength;

                           }
                           else
                           {
                               break;
                           }
                       }
                       _receiveBufferPosition = 0;
                   }
                   if (_serverSocket != null)
                   {
                       _serverSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, OnReceive, state);
                   }
               }
           }
           catch (Exception e)
           {
               _log.Fatal(e);
               throw;
           }
       }

       private void handleCompressedPacket(byte[] buffer, ref bool extended, ref PacketHandler handler, ref PacketReader reader)
       {
           int unpackSize = reader.ReadUInt16();
           byte[] Compressed = reader.ReadBytes();
           byte[] unpackBuffer = new byte[unpackSize];
           ZLibError error = ZLibError.Okay;

           if ((error = Compression.Unpack(unpackBuffer, ref unpackSize, Compressed, Compressed.Length)) != ZLibError.Okay)
           {
               throw new NetworkException("Compression " + error.ToString());
           }
           handler = GetHandler(buffer, 0, ref unpackSize, ref extended);

           if (_logPackets)
           {
               _log.Debug("Server - > Client");
               _log.Debug("Id: 0x{0:X2} Name: {1} Length: {2}", buffer[0], handler == null ? "Unknown" : handler.Name, buffer.Length);
               _log.Debug("\n{0}", Utility.FormatBuffer(buffer, buffer.Length));
           }

           if (handler != null)
           {
               reader = PacketReader.CreateInstance(buffer, buffer.Length, handler.Length.HasValue, extended);

               try
               {
                   handler.ReceiveAction.Invoke(reader);
               }
               catch (Exception e)
               {
                   _log.Error(e);
                   throw;
               }

           }
       }

       public PacketHandler GetHandler(byte[] buffer, int index, ref int packetLength, ref bool extended)
       {
           PacketHandler handler = _handlers[buffer[index]];

           if (handler == null)
           {
               extended = true;
               handler = GetExtendedHandler(buffer[index], (short)(buffer[index + 4] | (buffer[index + 3] << 8)));
           }
           if (handler != null)
           {
               packetLength = handler.Length ?? _receiveBuffer[index + 2] | (_receiveBuffer[index + 1] << 8);
           }
           return handler;
       }

       public PacketHandler GetExtendedHandler(byte cmd, short subCmd)
       {
           if (_extendedHandlers[cmd] == null)
           {
               return null;
           }

           return _extendedHandlers[cmd][subCmd];
       }

    }
}
